package com.googlecode.nacl_optimizer.tests;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import junit.framework.Assert;

import org.junit.Test;

import com.googlecode.nacl_optimizer.Optimizer;
import com.googlecode.nacl_optimizer.model.Binary;
import com.googlecode.nacl_optimizer.model.Block;
import com.googlecode.nacl_optimizer.model.CallInstruction;
import com.googlecode.nacl_optimizer.model.DirectNearCall;
import com.googlecode.nacl_optimizer.model.DirectNearJmp;
import com.googlecode.nacl_optimizer.model.Instruction;
import com.googlecode.nacl_optimizer.model.JmpInstruction;
import com.googlecode.nacl_optimizer.model.NopSeq;
import com.googlecode.nacl_optimizer.model.Procedure;


public class OptimizerTests
{
    @Test
    public void one_simple_block()
    {
        Block b = new Block(Arrays.asList(
                new Instruction(3,"add", "", "add"),
                new NopSeq(29, "", "nop")
              ));
        Procedure procedure = new Procedure(Arrays.asList(b), "", "");
        List<Procedure> procedures = Arrays.asList(procedure);
        Binary s = new Binary(procedures);
        Optimizer.optimize(s);
        Assert.assertEquals(1, s.getProcedures().size());
        Assert.assertEquals(1, s.getProcedures().get(0).getBlocks().size());
        Block b2 = s.getProcedures().get(0).getBlocks().get(0);
        Assert.assertEquals(3, b2.getInstructions().get(0).getLength());
    }
    
    @Test
    public void one_block_with_call()
    {
        Block b = new Block(Arrays.asList(
                new NopSeq(28, "",  "nop"),
                new DirectNearCall("", null)
              ));
        Procedure procedure = new Procedure(Arrays.asList(b), "", "");
        List<Procedure> procedures = Arrays.asList(procedure);
        Binary s = new Binary(procedures);
        Optimizer.optimize(s);
        Assert.assertEquals(1, s.getProcedures().size());
        Assert.assertEquals(1, s.getProcedures().get(0).getBlocks().size());
        Block b2 = s.getProcedures().get(0).getBlocks().get(0);
        Assert.assertEquals(4, b2.getInstructions().get(b2.getInstructions().size() - 1).getLength());
    }
    
    @Test
    public void remove_nops()
    {
        List<Instruction> i = new ArrayList<Instruction>();
        i.add( new NopSeq(20, "",  "nop"));
        i.add( new NopSeq(12, "", "nop"));
        Block b = new Block(i);
        List<Instruction> i1 = new ArrayList<Instruction>();
        i1.add( new NopSeq(28, "", "nop"));
        i1.add( new DirectNearCall("", "call"));
        Block b1 = new Block(i1);
        List<Block> bb = new ArrayList<Block>();
        bb.add(b);
        bb.add(b1);
        Procedure procedure = new Procedure(bb, "", "");
        List<Procedure> procedures = Arrays.asList(procedure);
        Binary s = new Binary(procedures);
        Optimizer.optimize(s);
        Assert.assertEquals(1, s.getProcedures().size());
        Assert.assertEquals(1, s.getProcedures().get(0).getBlocks().size());
        Block b2 = s.getProcedures().get(0).getBlocks().get(0);
        Assert.assertEquals(4, b2.getInstructions().get(b2.getInstructions().size() - 1).getLength());
        Assert.assertTrue( b2.getInstructions().get(0) instanceof JmpInstruction );
    }
    
    @Test
    public void simple_jump_to_call_through_nops()
    {
        List<Instruction> i = new ArrayList<Instruction>();
        i.add( new Instruction(10, "iadd", "", "iadd"));
        CallInstruction c = new DirectNearCall("", "call");
        i.add( new DirectNearJmp("", "jmp", c));
        i.add( new NopSeq(16, "", "nop"));
        i.add( c );
        Block b = new Block(i);
        List<Instruction> i1 = new ArrayList<Instruction>();
        i1.add( new NopSeq(28, "", "nop"));
        i1.add( new DirectNearCall("", "call"));
        Block b1 = new Block(i1);
        List<Block> bb = new ArrayList<Block>();
        bb.add(b);
        bb.add(b1);
        Procedure procedure = new Procedure(bb, "", "");
        List<Procedure> procedures = Arrays.asList(procedure);
        Binary s = new Binary(procedures);
        Optimizer.optimize(s);
        Assert.assertEquals(1, s.getProcedures().size());
        Assert.assertEquals(2, s.getProcedures().get(0).getBlocks().size());
        Block b2 = s.getProcedures().get(0).getBlocks().get(0);
        Assert.assertEquals(4, b2.getInstructions().get(b2.getInstructions().size() - 1).getLength());
        b2 = s.getProcedures().get(0).getBlocks().get(1);
        Assert.assertEquals(4, b2.getInstructions().get(b2.getInstructions().size() - 1).getLength());
    }
    
    @Test
    public void jump_to_call_through_nops()
    {
        List<Instruction> i0 = new ArrayList<Instruction>();
        i0.add( new Instruction(5, "iadd", "", "iadd" ));
        i0.add( new Instruction(5, "iadd", "", "iadd" ));
        i0.add( new Instruction(10, "isub", "", "isub" ));
        i0.add( new Instruction(5, "move", "", "move" ));
        i0.add( new Instruction(5, "move", "", "move" ));
        i0.add( new Instruction(2, "test", "", "test" ));
        
        List<Instruction> i = new ArrayList<Instruction>();
        i.add( new Instruction(10, "iadd", "", "iadd" ));
        CallInstruction c = new DirectNearCall("", "call");
        i.add( new DirectNearJmp("", "jmp", c));
        i.add( new NopSeq(16, "", "nop"));
        i.add( c );
        
        List<Instruction> i1 = new ArrayList<Instruction>();
        i1.add( new NopSeq(28, "", "nop"));
        i1.add( new DirectNearCall("", "call"));

        List<Block> bb = new ArrayList<Block>();
        bb.add(new Block(i0));
        bb.add(new Block(i));
        bb.add(new Block(i1));
        
        Binary s = new Binary(Arrays.asList(new Procedure(bb, "", "")));
        
        Optimizer.optimize(s);
        
        Assert.assertEquals(1, s.getProcedures().size());
        Assert.assertEquals(2, s.getProcedures().get(0).getBlocks().size());
        Block b2 = s.getProcedures().get(0).getBlocks().get(0);
        Assert.assertEquals(4, b2.getInstructions().get(b2.getInstructions().size() - 1).getLength());
        b2 = s.getProcedures().get(0).getBlocks().get(1);
        Assert.assertEquals(4, b2.getInstructions().get(b2.getInstructions().size() - 1).getLength());
    }
}
